gate(spec): register packages/spec/src/** doc blocks as the fourth symbol-anchor corpus, censused first - #17241
Merged
Conversation
…hor corpus The fourth corpus of the shared symbol-anchor resolver (scripts/symbol-anchors.mjs), joined by a `defineCorpus` call in the deliberately thin shape check-adr-symbol-anchors.mjs and check-scripts-symbol-anchors.mjs already use -- no second resolver. The census came first and the exit contract second. Measured on 08e38c6 in --list / --list-unresolvable reporting mode over 1,317 tracked .ts files: 198 raw extractAnchors over the whole file (counts spec's own fixture data) 189 through the commentProse projection (doc prose only) 6 ...and the cited path names a tracked file -- what this gate judges 7 hard findings across 6 files (6 line-anchor + 1 unresolved-path), 183 declined citations naming no tracked file, 3 soft cross-repo rows. That is a gate, not a migration -- the opposite of docs/adr/**'s 243-of-337 (72.1%). Repairing those 7 edits packages/spec/** text, which is domain:spec work with its own cards, so the exit contract is a pinned day-one residual: CENSUS_RESIDUAL enumerates the 7 sites with their repairs, every finding outside it is a hard red from day one, and a row whose citation gets repaired goes STALE and reds until it is deleted. Rows are keyed by citation TEXT, never by line -- a line-keyed row would be a line anchor inside the line-anchor gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
…erbatim `scripts/**` is itself a registered corpus, so the new gate's own header is swept by check-scripts-symbol-anchors. Quoting the websocket.zod.ts citation in anchor form -- and spelling the bare-word-colon-path shape literally -- made this file a finding against that corpus (1 unresolved-path, 1 soft cross-repo). Both are now described in words, with the reason stated inline so the next author does not reintroduce them. The citation itself is unchanged: it stays pinned in CENSUS_RESIDUAL as a string literal, which the commentProse projection blanks as code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
baozhoutao
marked this pull request as ready for review
September 9, 2026 21:25
baozhoutao
enabled auto-merge
September 9, 2026 21:25
baozhoutao
deleted the
claude/issue-17065-spec-docblock-anchor-corpus
branch
September 9, 2026 22:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clause-②: no
Fixes #17065
Registers
packages/spec/src/**doc blocks as the fourth corpus of the shared symbol-anchor resolver — adefineCorpuscall in the deliberately thin shapecheck-adr-symbol-anchors.mjsandcheck-scripts-symbol-anchors.mjsalready use. ⛔ No second resolver — the 2026-09-01 ruling on #13556 forbids one in terms, andscripts/symbol-anchors.mjsis untouched by this diff.⭐ The census came first, and here is the number
The card wrote the order hard and it was followed literally: register, run the reporting arms, take the count, then choose the exit contract. Measured on
08e38c6377over the 1,317 tracked.tsfiles underpackages/spec/src, with the instrument named beside every number because three instruments give three answers on one tree:extractAnchorsover the whole file (counts spec's own fixture data)commentProseprojection — doc prose onlyTotal hard findings: 7 across 6 of 1,317 files — the 6 line anchors plus 1
unresolved-path. Alongside them: 183 citations naming no tracked file (69 bare-filename, 62 directory-qualified, 52 continuation, across 34 files), and 3 softcross-repo-skippedrows.Controls, because a small number needs them more than a big one does. Every zero here is backed by a same-corpus positive control, using short fragments only:
counts.symbolis 0. Control:git grepfor a backticked path-hash-identifier span acrosspackages/spec/srcreturns exactly 1 file —api/websocket.zod.ts— and that one span is theunresolved-pathfinding. So the zero is "the convention has not reached this corpus", not "the extractor matched nothing".--list-unresolvableprints all 183 rows, and the self-test holdsdeclined.lengthequal to the counter.The bare-path axis, measured for the
checkBarePaths: falsecall: 2,635 bare path spans; 346 name a tracked file at the repo root, 610 resolve only when prefixed withpackages/spec/src/(spec doc blocks habitually cite package-relative), and 1,679 (758 distinct) bind against neither base. Judging them would produce 2,290 findings — the same calldocs/adr/**made at 1,056 andscripts/**at 1,617.The exit contract that number justified, and why
A pinned day-one residual, with the gate ON. All 7 findings live in
packages/spec/**text, which this card ⛔ forbids this lane from editing — repairing a spec doc block isdomain:specwork with its own cards. That left two honest options: a gate that can never fail, or a gate that fails on everything except a dated, enumerated, shrink-only residual.A gate that can never fail is the verifier AGENTS.md names as worse than no verifier, so
CENSUS_RESIDUALpins the 7 sites with their repairs and every finding outside it is a hard red from day one — which is exactly the property the card asked for: the fourth set does not have to be found by hand.⛔ It is not an exemption list. It is exact in both directions: a row whose citation gets repaired goes stale and reds until it is deleted, so a repair forces the row out in the same PR. Rows are keyed by citation text, never by line — a line-keyed row would be a line anchor inside the line-anchor gate, rotting the first time anyone added a paragraph above it.
Trigger 1 (rot comparable to 72.1%, or a count that makes this a migration): does NOT fire. 7 findings across 6 of 1,317 files is a gate, not a migration.
judgeUntrackedLineAnchorsflipped totrue, the same tree yields 189 findings on day one — which would be a migration. The small number is not a claim that spec doc blocks are clean; it is a statement about the citations a resolver living in this repo can bind. Both numbers are pinned inCENSUS_17065and the self-test holds them consistent, so the reassuring one can never be read without the other.Trigger 2 (a fifth hand-repair filed before this lands): does NOT fire. Searched with a positive control that the search reaches this very card (#17065 came back in the results); the open cards in this family are #16960 (the third repair) and #15809 (the
scripts/**declined-citation worklist). No fifth.⭐ An honest limit of this gate, stated because it is the card's own subject
The three
objectql/engine.ts:NNNNsites that #16960 owns are written with an abbreviated path, so they name no tracked file and land in the declined set — this gate would not have caught them. Same for therecord-validator.tssites #16441 repaired. The fourth set this registration stops from being found by hand is the set written with a repo-root path.That is not a defect in the registration — it is the same scope call
scripts/**made, whose residual became #15809 — but it would be dishonest to let the header imply otherwise, so the header says it in those words and--list-unresolvablekeeps the population a worklist rather than a number. Handed to the PM below rather than carded here.Verification
Ablation — the gate really can fail, both directions, on the live tree. Each leg: on-disk mutation proven by occurrence counts and a changed blob hash, run, restore proven byte-identical against the
HEADblob. ⛔ Nopackages/spec/**text was touched by either leg — both mutate the gate's ownCENSUS_RESIDUAL, which is what decides the exit contract.http-server.zod.tsresidual row (occurrences 1 to 0; blob955d9879toad18e70f)[line-anchor] packages/spec/src/system/http-server.zod.ts:238955d9879to8dabf07e)1 STALE CENSUS_RESIDUAL row(s)— and the self-test reds too955d9879identical to theHEADblob; green line backDerived gates.
node scripts/pm/dispatch-gates.mjs --commandsderived 63 families from the changeset; all 63 were run and reconciled with--ran: "63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED."One derived gate went genuinely red and was repaired:
check-scripts-symbol-anchors—scripts/**is itself a corpus, so the new gate's own header quoting a broken anchor verbatim made this file a finding. Both offending spellings are now described in words, with the reason stated inline. Both corpora are green on the final commit.Five commands exit 3 = PREREQUISITE NOT MET and are NOT MEASURED, not red — they read
dist/and want a fullpnpm build:check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:sourcemap-no-sources-content,check:type-check-debt. This diff touches no package source, so no package closure is affected; the full build belongs to CI.Re-run on the final commit
0be9a49284: both symbol-anchor corpora plus their self-tests, the ADR corpus, the shared resolver's self-test,check-step-collectors,check-self-test-workflow-commands,check-self-test-wired,check:nul-bytes,check:parse-guard,check:declared-population-live,check:watch-hint-literal,check:type-check-coverage— all exit 0.check:pm-dispatch-gates(the tool whose ledger prose this diff edits) passes: "dispatch-gates self-test: 1624 cases pass."Lint, as a declared narrowing rather than a repo-wide run. Targeted
eslint --no-inline-config --format jsonover the 2 changed.mjsfiles: 2 files linted, 0 errors, 0 warnings. The three pieces of evidence the narrowing needs: ① the population is read fromeslint.config.mjsitself, which lints only from the root; ② the file count is read from--format jsonoutput, not asserted; ③ the invariance claim is the config's own measured declaration — it "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file" — so this diff cannot move the verdict on any file it did not touch. The repo-widepnpm lintremains CI's run.skip-changeset, measured with a positive control. Nothing published moves: 70 published packages inspected, 0 namescripts/orlint.ymlinfiles[]; the control is that all 70 namedist, proving the probe reads real entries. The root manifest isprivate: true. All four paths are fast-track — repo-root config,.github/workflows/,scripts/**,scripts/pm/**.Acceptance notes — found, not fixed
scripts/**analogue, [finding] 96 of the 128 line citations inscripts/**gate headers name no tracked file, so the new symbol-anchor corpus declines them by name #15809, is scoped to that corpus only). They include the three sites [finding] threeobjectql/engine.ts:NNNNanchors in the doc block #16441 just repaired are off by up to 5,677 lines — outside that card's census, so they survived it #16960 owns. Enumerated by--list-unresolvableso a follow-up can be closed rather than re-measured. Handed to the PM rather than carded from this lane.repo:prefix is a bare lowercase word, so any bare-word-colon-path labelling convention inside a TypeScript doc block reads as a cross-repo anchor.shared/retired-key-migrate-sentence.test.tsdocuments its own corpus labels that way and lands 3 soft rows. Soft is the correct disposition — reported, never red — so this is a noted boundary, not a defect, and not filed.packages/spec/liveness/**and the rest ofpackages/specoutsidesrc/are outside this corpus.docPatternis.tsunderpackages/spec/src; the liveness ledger's citations are JSON and are gated separately bycheck:liveness.Not addressed here
⛔ Out of scope: #16962 remains open — that card's
@examplecaption convention is a different convention with no existing mechanism, deliberately not merged with this one. #16960 remains open and lands on its own; this PR ⛔ does not repair its three sites, and does not repair the 7 pinned residual sites either.Generated by Claude Code